package cn.louzen.utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperledger.fabric.protos.peer.Query;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.exception.TransactionEventException;
import org.hyperledger.fabric.sdk.security.CryptoSuite;

import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hyperledger.fabric.sdk.Channel.NOfEvents.createNofEvents;
import static org.hyperledger.fabric.sdk.Channel.TransactionOptions.createTransactionOptions;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

public class ChaincodeUtil {
    private static Log logger = LogFactory.getLog(SampleUserUtil.class);
    private static TransactionRequest.Type CHAIN_CODE_LANG = TransactionRequest.Type.GO_LANG;

    // 安装链码
    public static String installChaincode(Channel channel, User peerAdmin, final ChaincodeID chaincodeID)
            throws Exception {
        logger.info("安装链码-配置相关变量，开始");

        // 初始化一些变量
        final String channelName = channel.getName();
        Collection<Orderer> orderers = channel.getOrderers();
        Properties prop = Util.getProperties();

        // 分别是其他节点的：提案回复、其中成功的回复、其中失败的回复
        Collection<ProposalResponse> responses;
        Collection<ProposalResponse> successful = new LinkedList<>();
        Collection<ProposalResponse> failed = new LinkedList<>();

        logger.info("安装链码-配置相关变量，结束");
        logger.info("安装链码开始");

        // 设置客户端，生成并设置安装链码请求，
        HFClient client = HFClient.createNewInstance();
        client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
        client.setUserContext(peerAdmin);

        InstallProposalRequest installProposalRequest = client.newInstallProposalRequest();
        installProposalRequest.setChaincodeID(chaincodeID);
        installProposalRequest.setChaincodeSourceLocation(Paths.get(prop.getProperty("test_fixtures_path"),
                prop.getProperty("chain_code_filepath")).toFile());
        installProposalRequest.setChaincodeVersion(prop.getProperty("chain_code_version"));
        installProposalRequest.setChaincodeLanguage(CHAIN_CODE_LANG);

        Collection<Peer> peers = channel.getPeers();
        int numInstallProposal = peers.size();
        responses = client.sendInstallProposal(installProposalRequest, peers);

        logger.info("所有的回复、成功的和失败的都分别记录下来");
        for (ProposalResponse response : responses) {
            if (response.getStatus() == ProposalResponse.Status.SUCCESS) {
                logger.info(format("Successful install proposal response Txid: %s from peer %s",
                        response.getTransactionID(), response.getPeer().getName()));
                successful.add(response);
            } else {
                failed.add(response);
            }
        }
        logger.info(format("Received %d install proposal responses. Successful+verified: %d . Failed: %d",
                numInstallProposal, successful.size(), failed.size()));
        if (failed.size() > 0) {
            // 得到第一条失败的回复
            ProposalResponse first = failed.iterator().next();
            logger.info(format("Not enough endorsers for install :" + successful.size() +
                    ".  " + first.getMessage()));
            logger.info("安装链码结束");
            return prop.getProperty("chain_code_install_fail");
        }
        logger.info("安装链码结束");

        // 安装链码后再查一下相关Peer已经安装的链码
        logger.info("安装链码后再查一下相关Peer已经安装的链码");
        List<Query.ChaincodeInfo> installchaincodes = client.queryInstalledChaincodes(channel.getPeers().iterator().next());
        for (Query.ChaincodeInfo chaincodeInfo : installchaincodes) {
            logger.info(format(chaincodeInfo.getName()));
        }

        return prop.getProperty("chain_code_install_success");
    }

    // 初始化链码
    public static String initChaincode(Channel channel, User peerAdmin, final ChaincodeID chaincodeID,
                                       String[] args)
            throws Exception {
        logger.info("初始化链码，开始");

        // 初始化一些变量
        Properties prop = Util.getProperties();

        // 配置client
        HFClient client = HFClient.createNewInstance();
        client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
        client.setUserContext(peerAdmin);
        // 将channel存入client
//        byte[] serializedChannelBytes = channel.serializeChannel();
//        channel.shutdown(true);
//        channel = client.deSerializeChannel(serializedChannelBytes).initialize();

        // 分别是其他节点的：提案回复、其中成功的回复、其中失败的回复
        Collection<ProposalResponse> responses;
        Collection<ProposalResponse> successful = new LinkedList<>();
        Collection<ProposalResponse> failed = new LinkedList<>();

        // 配置提案请求
        InstantiateProposalRequest instantiateProposalRequest = client.newInstantiationProposalRequest();
        instantiateProposalRequest.setProposalWaitTime(Long.parseLong(prop.getProperty("proposalWaitTime")));
        instantiateProposalRequest.setChaincodeID(chaincodeID);
        instantiateProposalRequest.setChaincodeLanguage(CHAIN_CODE_LANG);
        instantiateProposalRequest.setFcn("init");
        // setArgs(new String[] { "a", "500", "b", "" + 200 })
        instantiateProposalRequest.setArgs(args);
        Map<String, byte[]> tm = new HashMap<>();
        tm.put("HyperLedgerFabric", "InstantiateProposalRequest:JavaSDK".getBytes(UTF_8));
        tm.put("method", "InstantiateProposalRequest".getBytes(UTF_8));
        instantiateProposalRequest.setTransientMap(tm);

        // 设置背书规则
        ChaincodeEndorsementPolicy chaincodeEndorsementPolicy = new ChaincodeEndorsementPolicy();
        chaincodeEndorsementPolicy.fromYamlFile(Paths.get(prop.getProperty("test_fixtures_path") +
                prop.getProperty("chaincodeendorsementpolicy.yaml")).toFile());
        instantiateProposalRequest.setChaincodeEndorsementPolicy(chaincodeEndorsementPolicy);

        // 向Peer节点发送初始化链码提案请求
        responses = channel.sendInstantiationProposal(instantiateProposalRequest, channel.getPeers());

        for (ProposalResponse response : responses) {
            if (response.isVerified() && response.getStatus() == ProposalResponse.Status.SUCCESS) {
                successful.add(response);
                logger.info(format("Succesful instantiate proposal response Txid: %s from peer %s",
                        response.getTransactionID(), response.getPeer().getName()));
            } else {
                failed.add(response);
            }
        }
        logger.info(format("Received %d instantiate proposal responses. Successful+verified: %d . Failed: %d",
                responses.size(), successful.size(), failed.size()));
        // 如果有Peer节点回复安装失败就分别输出
        if (failed.size() > 0) {
            for (ProposalResponse fail : failed) {
                logger.info(format("Not enough endorsers for instantiate :" + successful.size() +
                        "endorser failed with " + fail.getMessage() + ", on peer" + fail.getPeer()));
            }
            ProposalResponse first = failed.iterator().next();
            logger.info(format("Not enough endorsers for instantiate :" + successful.size() +
                    "endorser failed with " + first.getMessage() + ". Was verified:" + first.isVerified()));
            logger.info("初始化链码结束（存在Peer不通过）");
            return prop.getProperty("chain_code_init_fail" + " 初始化链码结束（存在Peer不通过）");
        }
        logger.info("初始化链码结束（所有Peer都通过）");

        // 向Orderer发送初始化交易信息（注意这里直接是交易了，不是交易请求，因为运行到这一步Peer节点都已经同意了）
        logger.info("向Orderer发送初始化交易信息，开始");
        Channel.NOfEvents nOfEvents = createNofEvents();
        if (!channel.getPeers(EnumSet.of(Peer.PeerRole.EVENT_SOURCE)).isEmpty()) {
            nOfEvents.addPeers(channel.getPeers(EnumSet.of(Peer.PeerRole.EVENT_SOURCE)));
        }
        if (!channel.getEventHubs().isEmpty()) {
            nOfEvents.addEventHubs(channel.getEventHubs());
        }
        channel.sendTransaction(successful, createTransactionOptions().userContext(client.getUserContext())
                .shuffleOrders(false).orderers(channel.getOrderers()).nOfEvents(nOfEvents))
                .thenApply(transactionEvent -> {
                    logger.info("## thenApply");
                    assertTrue(transactionEvent.isValid()); // must be valid to be here.
                    logger.info(format("## transactionEvent.isValid(): " + transactionEvent.isValid()));
                    assertNotNull(transactionEvent.getSignature()); // musth have a signature.
                    logger.info(format("## transactionEvent.getSignature(): " + transactionEvent.getSignature()));
                    BlockEvent blockEvent = transactionEvent.getBlockEvent(); // This is the blockevent that has this
                    // transaction.
                    assertNotNull(blockEvent.getBlock()); // Make sure the RAW Fabric block is returned.
                    // logger.info(format("## blockEvent.getBlock(): " + blockEvent.getBlock()));

                    logger.info(format("## Finished instantiate transaction with transaction id %s",
                            transactionEvent.getTransactionID()));
                    return null;
                }).exceptionally(e -> {
            // 异常处理
            if (e instanceof TransactionEventException) {
                BlockEvent.TransactionEvent te = ((TransactionEventException) e).getTransactionEvent();
                if (te != null) {
                    throw new AssertionError(
                            format("Transaction with txid %s failed. %s", te.getTransactionID(), e.getMessage()),
                            e);
                }
            }

            throw new AssertionError(
                    format("Test failed with %s exception %s", e.getClass().getName(), e.getMessage()), e);

        }).get(Long.parseLong(prop.getProperty("trsactionWaitTime")), TimeUnit.SECONDS);
        logger.info("向Orderer发送初始化交易信息，结束");

        return prop.getProperty("chain_code_init_success");
    }

    // 添加一条数据，fcn是方法名，args是参数
    public static String addTransaction(Channel channel, User user, final ChaincodeID chaincodeID,
                                        String[] args) throws Exception {
        logger.info("添加数据，开始");

        // 初始化一些变量
        Properties prop = Util.getProperties();
        try {
            logger.info("向Peer发送提案请求，开始");

            // 分别是其他节点的：提案回复、其中成功的回复、其中失败的回复
            Collection<ProposalResponse> responses;
            Collection<ProposalResponse> successful = new LinkedList<>();
            Collection<ProposalResponse> failed = new LinkedList<>();

            // 配置client
            HFClient client = HFClient.createNewInstance();
            client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
            client.setUserContext(user);

            // 配置提案请求
            TransactionProposalRequest transactionProposalRequest = client.newTransactionProposalRequest();
            transactionProposalRequest.setChaincodeID(chaincodeID);
            transactionProposalRequest.setChaincodeLanguage(CHAIN_CODE_LANG);
            // transactionProposalRequest.setFcn("invoke");
            transactionProposalRequest.setFcn("move");
            transactionProposalRequest.setProposalWaitTime(Long.parseLong(prop.getProperty("proposalWaitTime")));
            transactionProposalRequest.setArgs(args);

            Map<String, byte[]> tm2 = new HashMap<>();
            // Just some extra junk in transient map
            tm2.put("HyperLedgerFabric", "TransactionProposalRequest:JavaSDK".getBytes(UTF_8));
            tm2.put("method", "TransactionProposalRequest".getBytes(UTF_8)); // 同上
            // 下面这个":)"对应链码中定义的返回值，返回这个就代表链码运行成功
            tm2.put("result", ":)".getBytes(UTF_8)); // This should be returned see chaincode why.
            tm2.put("event", "!".getBytes(UTF_8)); // This should trigger an event see chaincode why.

            transactionProposalRequest.setTransientMap(tm2);

            // 向Peer节点发送添加交易信息的提案
            Collection<ProposalResponse> transactionPropResp = channel
                    .sendTransactionProposal(transactionProposalRequest, channel.getPeers());

            // 接下来就是烦躁的检查提案回复的过程
            // 查看提案有多少回复，和其中有多少成功的、失败的
            for (ProposalResponse response : transactionPropResp) {
                if (response.getStatus() == ProposalResponse.Status.SUCCESS) {
                    logger.info(format("Successful transaction proposal response Txid: %s from peer %s",
                            response.getTransactionID(), response.getPeer().getName()));
                    successful.add(response);
                } else {
                    failed.add(response);
                }
            }
            Collection<Set<ProposalResponse>> proposalConsistencySets = SDKUtils
                    .getProposalConsistencySets(transactionPropResp);
            if (proposalConsistencySets.size() != 1) {
                logger.info(format("Expected only one set of consistent proposal responses but got %d",
                        proposalConsistencySets.size()));
                logger.info("向Peer发送提案请求，失败");
                return prop.getProperty("chain_code_add_fail");
            }
            logger.info(format("Received %d transaction proposal responses. Successful+verified: %d . Failed: %d",
                    transactionPropResp.size(), successful.size(), failed.size()));
            if (failed.size() > 0) {
                ProposalResponse firstTransactionProposalResponse = failed.iterator().next();
                logger.info(format("Not enough endorsers for invoke(move a,b,100):" + failed.size() + " endorser error: "
                        + firstTransactionProposalResponse.getMessage() + ". Was verified: "
                        + firstTransactionProposalResponse.isVerified()));
                logger.info("向Peer发送提案请求，失败");
                return prop.getProperty("chain_code_add_fail");
            }
            logger.info("向Peer发送提案请求，成功");
            logger.info("向Orderer发送提案，开始");
            channel.sendTransaction(successful).get(Long.parseLong(prop.getProperty("trsactionWaitTime")),
                    TimeUnit.SECONDS);
            logger.info("向Orderer发送提案，结束");
        } catch (Exception e) {
            logger.error("Caught an exception while invoking chaincode");
            e.printStackTrace();
            logger.error("Failed invoking chaincode with error : " + e.getMessage());
        }
        logger.info("添加数据，结束");
        return prop.getProperty("chain_code_add_success");
    }

    // 查询操作
    public static String queryTransaction(Channel channel, User user, final ChaincodeID chaincodeID,
                                          String[] args) throws Exception {
        logger.info("查询数据，开始");
        // 初始化一些变量
        Properties prop = Util.getProperties();

        // 配置client
        HFClient client = HFClient.createNewInstance();
        client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
        client.setUserContext(user);

        QueryByChaincodeRequest queryByChaincodeRequest = client.newQueryProposalRequest();
        queryByChaincodeRequest.setChaincodeID(chaincodeID);
        queryByChaincodeRequest.setFcn("query");
        queryByChaincodeRequest.setArgs(args);

//        Map<String, byte[]> tm2 = new HashMap<>();
//        tm2.put("HyperLedgerFabric", "QueryByChaincodeRequest:JavaSDK".getBytes(UTF_8));
//        tm2.put("method", "QueryByChaincodeRequest".getBytes(UTF_8));
//        queryByChaincodeRequest.setTransientMap(tm2);

        // 查询链码前先查一下当前Peer加入了哪些Channel
        logger.info("查询链码前先查一下相关Peer已经加入的Channel");
        Set<String> peerchannels = client.queryChannels(channel.getPeers().iterator().next());
        for (String peerchannel : peerchannels) {
            logger.info(format(peerchannel));
        }

        // 查询链码前先查一下相关Peer已经实例化的链码
        logger.info("查询链码前先查一下相关Peer已经实例化的链码");
        List<Query.ChaincodeInfo> instancechaincodes = channel.queryInstantiatedChaincodes(channel.getPeers().iterator().next());
        logger.info("instancechaincodes.size: " + instancechaincodes.size());
        for (Query.ChaincodeInfo instancechaincode : instancechaincodes) {
            logger.info(format(instancechaincode.getName()));
        }

        logger.info("查询链码，开始");
        Collection<ProposalResponse> queryProposals = channel.queryByChaincode(queryByChaincodeRequest,
                channel.getPeers());
        logger.info("查询链码，结束");

        for (ProposalResponse proposalResponse : queryProposals) {
            if (!proposalResponse.isVerified()
                    || proposalResponse.getStatus() != ProposalResponse.Status.SUCCESS) {
                logger.info(format("Failed query proposal from peer " + proposalResponse.getPeer().getName() +
                        " status: " + proposalResponse.getStatus() + ". Messages: " + proposalResponse.getMessage() +
                        ". Was verified : " + proposalResponse.isVerified()));
                logger.info("查询数据，失败");
                return prop.getProperty("chain_code_query_fail");
            } else {
                String payload = proposalResponse.getProposalResponse().getResponse().getPayload()
                        .toStringUtf8();
                logger.info(format("Query payload of the value from peer %s returned %s",
                        proposalResponse.getPeer().getName(), payload));
            }
        }
        String payload = queryProposals.iterator().next().getProposalResponse().getResponse().getPayload()
                .toStringUtf8();
        logger.info("查询数据，结束");
        return format(prop.getProperty("chain_code_query_success") + "." + payload);
    }

    public static ChaincodeID getChaincodeID(String chainCodeName, String chainCodeVersion, String chainCodePath) {
        ChaincodeID.Builder chaincodeIDBuilder = ChaincodeID.newBuilder().setName(chainCodeName)
                .setVersion(chainCodeVersion).setPath(chainCodePath);
        ChaincodeID chaincodeID = chaincodeIDBuilder.build();
        return chaincodeID;
    }

    public static ChaincodeID getChaincodeID(String chainCodeName, String chainCodeVersion) {
        ChaincodeID.Builder chaincodeIDBuilder = ChaincodeID.newBuilder().setName(chainCodeName)
                .setVersion(chainCodeVersion);
        ChaincodeID chaincodeID = chaincodeIDBuilder.build();
        return chaincodeID;
    }
}
